import Dep, { popTarget, pushTarget } from "./dep"

let id = 0

// 每个属性有一个 dep（属性是被观察者），watcher就是观察者（属性变化了会通知观察者来更新）=> 观察者模式
export default class Watcher {
  constructor (vm ,exprOrFn, options, cb) {
    this.vm = vm
    this.id = id++
    this.renderWatcher = options

    if (typeof exprOrFn === 'string') {
      this.getter = function () {
        return vm[exprOrFn]
      }
    } else {
      this.getter = exprOrFn
    }

    this.deps = []
    this.depsId = new Set()
    this.lazy = options.lazy
    this.dirty = this.lazy // 缓存值
    this.cb = cb
    this.user = options.user  // 用户watcher

    this.value = this.lazy ? undefined : this.get() // 旧值
  }

  get () {
    pushTarget(this)
    const value = this.getter.call(this.vm)
    popTarget()

    return value
  }

  evaluate () {
    this.value = this.get() // 获取到用户的返回值，表示为脏
    this.dirty = false
  }

  addDep (dep) { // 一个组件对应多个属性 重复的属性不用记录
    const id = dep.id
    if (!this.depsId.has(id)) {
      this.depsId.add(id)
      this.deps.push(dep)
      dep.addSub(this)
    }
  }

  depend () {
    let i = this.deps.length
    while(i--) {
      // 让计算属性watcher 也收集渲染watcher
      this.deps[i].depend()
    }
  }

  update () {
    if (this.lazy) {
      this.dirty = true
    } else {
      queueWatcher(this)
      // this.get()
    }
  }

  run () {
    this.oldVal = this.value
    this.newVal = this.get() // 新值
    if (this.user) {
      this.cb.call(this.vm, this.newVal, this.oldVal)
    }
  }
}

// 给每个属性增加一个dep，目的是收集watcher

let queue = []
let has = {}
let pending = false // 防抖 进行批处理

function queueWatcher (watcher) {
  const id = watcher.id
  if (!has[id]) {
    queue.push(watcher)
    has[id] = true
    // 不管我们的update执行多少次，最终只执行一轮刷新操作
    if (!pending) {
      nextTick(flushSchedulerQueue)
      pending = true
    }
  }
}

function flushSchedulerQueue () {
  const flushQueue = queue.slice(0)
  queue = []
  has = {}
  pending = false
  flushQueue.forEach(q => q.run())
}

let callbacks = []
let waiting = false

function flushCallbacks () {
  waiting = false
  const cbs = callbacks.slice(0)
  callbacks = []
  cbs.forEach(cb => cb())
}

let timerFunc
if (Promise) {
  timerFunc = () => {
    Promise.resolve().then(flushCallbacks)
  }
} else if (MutationObserver) {
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(1)
  observer.observe(textNode, {
    characterData: true
  })
  textNode.textContent = 2
} else if (setImmediate) {
  timerFunc = setImmediate(flushCallbacks)
} else {
  timerFunc = setTimeout(flushCallbacks)
}

export function nextTick (cb) {
  callbacks.push(cb)
  if (!waiting) {
    timerFunc()
    waiting = true
  }
}